package org.hawk.gwt.ppc;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.hawk.gwt.ppc.javacc.ParseException;
import org.hawk.gwt.ppc.utils.Name;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
 * Represents GWT module descriptor.
 * Allows user to manipulate the content of this descriptor.
 * @author alex.bereznevatiy@gmail.com
 */
public class GWTModule {
	private static final String INHERITS_MODULE_NAME_ATTRIBUTE = "name";

	private static final String INHERITS_ELEMENT = "inherits";

	private static final String ROOT_MODULE = "com.google.gwt.core.Core";

	private static final String SOURCE_ELEMENT = "source";

	private static final String SOURCE_SUBPACKAGE_ATTRIBUTE = "path";

	private static final String ENTRY_POINT_ELEMENT = "entry-point";

	private static final String ENTRY_POINT_CLASS_ATTRIBUTE = "class";

	private static final String SUPER_SOURCE_ELEMENT = "super-source";
	
	private String name;
	private Document root;
	
	/**
	 * Creates module descriptor by parsing the 
	 * XML descriptor file with {@link URL} specified.
	 * @param name
	 * @param moduleUrl
	 * @throws ParseException
	 */
	public GWTModule(String name, URL moduleUrl) throws ParseException {
		this.name = name;
		try {
			InputStream is = moduleUrl.openStream();
			this.root = DocumentBuilderFactory.newInstance()
				.newDocumentBuilder().parse(is);
			is.close();
		} catch(Exception ex) {
			throw new ParseException(ex);
		}

	}
	
	/**
	 * Gets list of modules this module inherited from.
	 * This modules are discovered by configuration elements:
	 * &lt;inherits name='[module name]'/&gt;
	 * @return list of all module inherited by this one.
	 */
	public String[] getInheritedModules() {
		return getAttributesArray(getElementsWithName(INHERITS_ELEMENT),
			INHERITS_MODULE_NAME_ATTRIBUTE, null, ROOT_MODULE);
	}
	
	/**
	 * Gets list of packages where sources of this module are located.
	 * This packages are discovered by configuration elements:
	 * &lt;source path='[subpackage name]'/&gt;
	 * and
	 * &lt;super-source path='[subpackage name]'/&gt;
	 * @return list of all source packages of this module.
	 */
	public String[] getClientPackages() {
		return getAttributesArray(getElementsWithNames(SOURCE_ELEMENT,SUPER_SOURCE_ELEMENT),
			SOURCE_SUBPACKAGE_ATTRIBUTE, Name.getParentJavaName(name), null);
	}
	
	/**
	 * Gets list of entry points of this module.
	 * This classes are discovered by configuration elements:
	 * &lt;entry-point class='[entry point class name]'/&gt;
	 * @return list of all module inherited by this one.
	 */
	public String[] getEntryPoints() {
		return getAttributesArray(getElementsWithName(ENTRY_POINT_ELEMENT), ENTRY_POINT_CLASS_ATTRIBUTE, null, null);
	}
	
	/**
	 * Adds inheritance to this GWT module.
	 * @param moduleName
	 * @throws ParseException 
	 * @throws IOException 
	 */
	public void addInheritance(String moduleName) throws IOException, ParseException {
		ProcessingUnitsManager.getInstance().processModule(moduleName);
		
		if(Arrays.asList(getInheritedModules()).contains(moduleName)) {
			return;
		}
		Element element = root.createElement(INHERITS_ELEMENT);
		element.setAttribute(INHERITS_MODULE_NAME_ATTRIBUTE, moduleName);
		root.getDocumentElement().appendChild(element);
	}
	
	/**
	 * @return full name of this module.
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * @return stream representing this GWT module descriptor.
	 * @throws IOException 
	 */
	public InputStream toStream() throws IOException {
		try {
			ByteArrayOutputStream stream = new ByteArrayOutputStream(); 
			Properties props = new Properties();
			props.put(OutputKeys.METHOD, "xml");
			Transformer transformer = TransformerFactory.newInstance().newTransformer();
			transformer.setOutputProperties(props);
			transformer.transform(
				new DOMSource(root.getDocumentElement()), 
				new StreamResult(stream));
	
			return new ByteArrayInputStream(stream.toByteArray());
		} catch(TransformerException ex){
			throw new IOException(ex.getMessage());
		}
	}
	
	private List<Element> getElementsWithNames(String...names) {
		List<Element> result = new LinkedList<Element>();
		for(String name : names) {
			result.addAll(getElementsWithName(name));
		}
		return result;
	}

	private List<Element> getElementsWithName(String name) {
		NodeList config = root.getDocumentElement().getChildNodes();
		List<Element> result = new LinkedList<Element>();
		for(int i = 0; i < config.getLength(); i++){
			Node item = config.item(i);
			if ((item instanceof Element) && item.getNodeName().equals(name)) {
				result.add((Element)item);
			}
		}
		return result;
	}
	
	private String[] getAttributesArray(List<Element> elements, 
			String attribute, String prefix, String exclude) {
		List<String> result = new LinkedList<String>();
		for(Element el : elements) {
			String value = el.getAttribute(attribute);
			if(value == null || value.trim().length() == 0 ||
				(exclude != null && exclude.equals(value))) {
				continue;
			}
			if(prefix != null) {
				value = Name.getChildJavaName(prefix, value);
			}
			result.add(value);
		}
		return result.toArray(new String[result.size()]);
	}
	
	@Override
	public String toString() {
		return "GWT Module "+name;
	}
}
